﻿using System;
using System.IO;
using SoundServant.Lame;
using SoundServant.WaveLib;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;

namespace SoundServant
{
    /// <summary>
    /// Convert PCM audio data to PCM format
    /// The data received through the method write is assumed as PCM audio data. 
    /// This data is converted to MP3 format and written to the result stream. 
    /// <seealso cref="SoundServant.mmedia.utils.AudioFileWriter"/>
    /// <seealso cref="SoundServant.Lame"/>
    /// </summary>
    public class Mp3Writer : AudioWriter
    {
        private bool closed = false;
        private BE_CONFIG m_Mp3Config = null;
        private uint m_hLameStream = 0;
        private uint m_InputSamples = 0;
        private uint m_OutBufferSize = 0;
        private byte[] m_InBuffer = null;
        private int m_InBufferPos = 0;
        private byte[] m_OutBuffer = null;

        /// <summary>
        /// Create a Mp3Writer with the default MP3 format
        /// </summary>
        /// <param name="Output">Stream that will hold the MP3 resulting data</param>
        /// <param name="InputDataFormat">PCM format of input data</param>
        public Mp3Writer(Stream Output, WaveFormat InputDataFormat)
            : this(Output, InputDataFormat, new BE_CONFIG(InputDataFormat))
        {
        }

        /// <summary>
        /// Create a Mp3Writer with specific MP3 format
        /// </summary>
        /// <param name="Output">Stream that will hold the MP3 resulting data</param>
        /// <param name="cfg">Writer Config</param>
        public Mp3Writer(Stream Output, Mp3WriterConfig cfg)
            : this(Output, cfg.Format, cfg.Mp3Config)
        {
        }

        /// <summary>
        /// Create a Mp3Writer with specific MP3 format
        /// </summary>
        /// <param name="Output">Stream that will hold the MP3 resulting data</param>
        /// <param name="InputDataFormat">PCM format of input data</param>
        /// <param name="Mp3Config">Desired MP3 config</param>
        public Mp3Writer(Stream Output, WaveFormat InputDataFormat, BE_CONFIG Mp3Config)
            : base(Output, InputDataFormat)
        {
            try
            {
                m_Mp3Config = Mp3Config;
                uint LameResult = Lame_encDll.beInitStream(m_Mp3Config, ref m_InputSamples, ref m_OutBufferSize, ref m_hLameStream);
                if (LameResult != Lame_encDll.BE_ERR_SUCCESSFUL)
                {
                    throw new ApplicationException(string.Format("Lame_encDll.beInitStream failed with the error code {0}", LameResult));
                }
                m_InBuffer = new byte[m_InputSamples * 2]; //Input buffer is expected as short[]
                m_OutBuffer = new byte[m_OutBufferSize];
            }
            catch
            {
                base.Close();
                throw;
            }
        }

        /// <summary>
        /// MP3 Config of final data
        /// </summary>
        public BE_CONFIG Mp3Config
        {
            get
            {
                return m_Mp3Config;
            }
        }

        protected override int GetOptimalBufferSize()
        {
            return m_InBuffer.Length;
        }

        public override void Close()
        {
            if (!closed)
            {
                try
                {
                    uint EncodedSize = 0;
                    if (m_InBufferPos > 0)
                    {
                        if (Lame_encDll.EncodeChunk(m_hLameStream, m_InBuffer, 0, (uint)m_InBufferPos, m_OutBuffer, ref EncodedSize) == Lame_encDll.BE_ERR_SUCCESSFUL)
                        {
                            if (EncodedSize > 0)
                            {
                                base.Write(m_OutBuffer, 0, (int)EncodedSize);
                            }
                        }
                    }
                    EncodedSize = 0;
                    if (Lame_encDll.beDeinitStream(m_hLameStream, m_OutBuffer, ref EncodedSize) == Lame_encDll.BE_ERR_SUCCESSFUL)
                    {
                        if (EncodedSize > 0)
                        {
                            base.Write(m_OutBuffer, 0, (int)EncodedSize);
                        }
                    }
                }
                finally
                {
                    Lame_encDll.beCloseStream(m_hLameStream);
                }
            }
            closed = true;
            base.Close();
        }


        /// <summary>
        /// Send to the compressor an array of bytes.
        /// </summary>
        /// <param name="buffer">Input buffer</param>
        /// <param name="index">Start position</param>
        /// <param name="count">Bytes to process. The optimal size, to avoid buffer copy, is a multiple of <see cref="SoundServant.mmedia.utils.AudioFileWriter.OptimalBufferSize"/></param>
        public override void Write(byte[] buffer, int index, int count)
        {
            int ToCopy = 0;
            uint EncodedSize = 0;
            uint LameResult;
            while (count > 0)
            {
                if (m_InBufferPos > 0)
                {
                    ToCopy = Math.Min(count, m_InBuffer.Length - m_InBufferPos);
                    Buffer.BlockCopy(buffer, index, m_InBuffer, m_InBufferPos, ToCopy);
                    m_InBufferPos += ToCopy;
                    index += ToCopy;
                    count -= ToCopy;
                    if (m_InBufferPos >= m_InBuffer.Length)
                    {
                        m_InBufferPos = 0;
                        if ((LameResult = Lame_encDll.EncodeChunk(m_hLameStream, m_InBuffer, m_OutBuffer, ref EncodedSize)) == Lame_encDll.BE_ERR_SUCCESSFUL)
                        {
                            if (EncodedSize > 0)
                            {
                                base.Write(m_OutBuffer, 0, (int)EncodedSize);
                            }
                        }
                        else
                        {
                            throw new ApplicationException(string.Format("Lame_encDll.EncodeChunk failed with the error code {0}", LameResult));
                        }
                    }
                }
                else
                {
                    if (count >= m_InBuffer.Length)
                    {
                        if ((LameResult = Lame_encDll.EncodeChunk(m_hLameStream, buffer, index, (uint)m_InBuffer.Length, m_OutBuffer, ref EncodedSize)) == Lame_encDll.BE_ERR_SUCCESSFUL)
                        {
                            if (EncodedSize > 0)
                            {
                                base.Write(m_OutBuffer, 0, (int)EncodedSize);
                            }
                        }
                        else
                        {
                            throw new ApplicationException(string.Format("Lame_encDll.EncodeChunk failed with the error code {0}", LameResult));
                        }
                        count -= m_InBuffer.Length;
                        index += m_InBuffer.Length;
                    }
                    else
                    {
                        Buffer.BlockCopy(buffer, index, m_InBuffer, 0, count);
                        m_InBufferPos = count;
                        index += count;
                        count = 0;
                    }
                }
            }
        }

        /// <summary>
        /// Send to the compressor an array of bytes.
        /// </summary>
        /// <param name="buffer">The optimal size, to avoid buffer copy, is a multiple of <see cref="SoundServant.mmedia.utils.AudioFileWriter.OptimalBufferSize"/></param>
        public override void Write(byte[] buffer)
        {
            this.Write(buffer, 0, buffer.Length);
        }


    }




    public abstract class AudioWriter : BinaryWriter
    {
        protected WaveFormat m_InputDataFormat;

        public AudioWriter(Stream Output, WaveFormat InputDataFormat)
            : base(Output, System.Text.Encoding.ASCII)
        {
            m_InputDataFormat = InputDataFormat;
        }


        public AudioWriter(Stream Output, AudioWriterConfig Config)
            : this(Output, Config.Format)
        {
        }


        protected abstract int GetOptimalBufferSize();

        private static int m_ConfigWidth = 368;
        private static int m_ConfigHeight = 264;

        protected virtual AudioWriterConfig GetWriterConfig()
        {
            return new AudioWriterConfig(m_InputDataFormat);
        }

        public AudioWriterConfig WriterConfig
        {
            get
            {
                return GetWriterConfig();
            }
        }

        /// <summary>
        /// Width of the config control
        /// </summary>
        public static int ConfigWidth
        {
            get
            {
                return m_ConfigWidth;
            }
            set
            {
                m_ConfigWidth = value;
            }
        }

        /// <summary>
        /// Height of the config control
        /// </summary>
        public static int ConfigHeight
        {
            get
            {
                return m_ConfigHeight;
            }
            set
            {
                m_ConfigHeight = value;
            }
        }

        /// <summary>
        /// Optimal size of the buffer used in each write operation to obtain best performance.
        /// This value must be greater than 0 
        /// </summary>
        public int OptimalBufferSize
        {
            get
            {
                return GetOptimalBufferSize();
            }
        }

        public override void Write(string value)
        {
            throw new NotSupportedException("Write(string value) is not supported");
        }

        public override void Write(float value)
        {
            throw new NotSupportedException("Write(float value) is not supported");
        }

        public override void Write(ulong value)
        {
            throw new NotSupportedException("Write(ulong value) is not supported");
        }

        public override void Write(long value)
        {
            throw new NotSupportedException("Write(long value) is not supported");
        }

        public override void Write(uint value)
        {
            throw new NotSupportedException("Write(uint value) is not supported");
        }

        public override void Write(int value)
        {
            throw new NotSupportedException("Write(int value) is not supported");
        }

        public override void Write(ushort value)
        {
            throw new NotSupportedException("Write(ushort value) is not supported");
        }

        public override void Write(short value)
        {
            throw new NotSupportedException("Write(short value) is not supported");
        }

        public override void Write(decimal value)
        {
            throw new NotSupportedException("Write(decimal value) is not supported");
        }

        public override void Write(double value)
        {
            throw new NotSupportedException("Write(double value) is not supported");
        }

        public override void Write(char[] chars, int index, int count)
        {
            throw new NotSupportedException("Write(char[] chars, int index, int count) is not supported");
        }

        public override void Write(char[] chars)
        {
            throw new NotSupportedException("Write(char[] chars) is not supported");
        }

        public override void Write(char ch)
        {
            throw new NotSupportedException("Write(char ch) is not supported");
        }

        public override void Write(sbyte value)
        {
            throw new NotSupportedException("Write(sbyte value) is not supported");
        }

        public override void Write(byte value)
        {
            throw new NotSupportedException("Write(byte value) is not supported");
        }

        public override void Write(bool value)
        {
            throw new NotSupportedException("Write(bool value) is not supported");
        }
    }
}

namespace SoundServant.Lame
{
    public enum VBRMETHOD : int
    {
        VBR_METHOD_NONE = -1,
        VBR_METHOD_DEFAULT = 0,
        VBR_METHOD_OLD = 1,
        VBR_METHOD_NEW = 2,
        VBR_METHOD_MTRH = 3,
        VBR_METHOD_ABR = 4
    }

    /* MPEG modes */
    public enum MpegMode : uint
    {
        STEREO = 0,
        JOINT_STEREO,
        DUAL_CHANNEL,   /* LAME doesn't supports this! */
        MONO,
        NOT_SET,
        MAX_INDICATOR   /* Don't use this! It's used for sanity checks. */
    }

    public enum LAME_QUALITY_PRESET : int
    {
        LQP_NOPRESET = -1,
        // QUALITY PRESETS
        LQP_NORMAL_QUALITY = 0,
        LQP_LOW_QUALITY = 1,
        LQP_HIGH_QUALITY = 2,
        LQP_VOICE_QUALITY = 3,
        LQP_R3MIX = 4,
        LQP_VERYHIGH_QUALITY = 5,
        LQP_STANDARD = 6,
        LQP_FAST_STANDARD = 7,
        LQP_EXTREME = 8,
        LQP_FAST_EXTREME = 9,
        LQP_INSANE = 10,
        LQP_ABR = 11,
        LQP_CBR = 12,
        LQP_MEDIUM = 13,
        LQP_FAST_MEDIUM = 14,
        // NEW PRESET VALUES
        LQP_PHONE = 1000,
        LQP_SW = 2000,
        LQP_AM = 3000,
        LQP_FM = 4000,
        LQP_VOICE = 5000,
        LQP_RADIO = 6000,
        LQP_TAPE = 7000,
        LQP_HIFI = 8000,
        LQP_CD = 9000,
        LQP_STUDIO = 10000
    }

    [StructLayout(LayoutKind.Sequential), Serializable]
    public struct MP3 //BE_CONFIG_MP3
    {
        public uint dwSampleRate;		// 48000, 44100 and 32000 allowed
        public byte byMode;			// BE_MP3_MODE_STEREO, BE_MP3_MODE_DUALCHANNEL, BE_MP3_MODE_MONO
        public ushort wBitrate;		// 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256 and 320 allowed
        public int bPrivate;
        public int bCRC;
        public int bCopyright;
        public int bOriginal;
    }

    [StructLayout(LayoutKind.Sequential, Size = 327), Serializable]
    public struct LHV1 // BE_CONFIG_LAME LAME header version 1
    {
        public const uint MPEG1 = 1;
        public const uint MPEG2 = 0;

        // STRUCTURE INFORMATION
        public uint dwStructVersion;
        public uint dwStructSize;
        // BASIC ENCODER SETTINGS
        public uint dwSampleRate;		// SAMPLERATE OF INPUT FILE
        public uint dwReSampleRate;		// DOWNSAMPLERATE, 0=ENCODER DECIDES  
        public MpegMode nMode;				// STEREO, MONO
        public uint dwBitrate;			// CBR bitrate, VBR min bitrate
        public uint dwMaxBitrate;		// CBR ignored, VBR Max bitrate
        public LAME_QUALITY_PRESET nPreset;			// Quality preset
        public uint dwMpegVersion;		// MPEG-1 OR MPEG-2
        public uint dwPsyModel;			// FUTURE USE, SET TO 0
        public uint dwEmphasis;			// FUTURE USE, SET TO 0
        // BIT STREAM SETTINGS
        public int bPrivate;			// Set Private Bit (TRUE/FALSE)
        public int bCRC;				// Insert CRC (TRUE/FALSE)
        public int bCopyright;			// Set Copyright Bit (TRUE/FALSE)
        public int bOriginal;			// Set Original Bit (TRUE/FALSE)
        // VBR STUFF
        public int bWriteVBRHeader;	// WRITE XING VBR HEADER (TRUE/FALSE)
        public int bEnableVBR;			// USE VBR ENCODING (TRUE/FALSE)
        public int nVBRQuality;		// VBR QUALITY 0..9
        public uint dwVbrAbr_bps;		// Use ABR in stead of nVBRQuality
        public VBRMETHOD nVbrMethod;
        public int bNoRes;				// Disable Bit resorvoir (TRUE/FALSE)
        // MISC SETTINGS
        public int bStrictIso;			// Use strict ISO encoding rules (TRUE/FALSE)
        public ushort nQuality;			// Quality Setting, HIGH BYTE should be NOT LOW byte, otherwhise quality=5
        // FUTURE USE, SET TO 0, align strucutre to 331 bytes
        //[ MarshalAs( UnmanagedType.ByValArray, SizeConst=255-4*4-2 )]
        //public byte[]   btReserved;//[255-4*sizeof(DWORD) - sizeof( WORD )];
        public LHV1(WaveFormat format, uint MpeBitRate, uint ReSampleRate)
        {
            if (format.wFormatTag != (short)WaveFormats.Pcm)
            {
                throw new ArgumentOutOfRangeException("format", "Only PCM format supported");
            }
            if (format.wBitsPerSample != 16)
            {
                throw new ArgumentOutOfRangeException("format", "Only 16 bits samples supported");
            }
            dwStructVersion = 1;
            dwStructSize = (uint)Marshal.SizeOf(typeof(BE_CONFIG));
            switch (format.nSamplesPerSec)
            {
                case 16000:
                case 22050:
                case 24000:
                    dwMpegVersion = MPEG2;
                    break;
                case 32000:
                case 44100:
                case 48000:
                    dwMpegVersion = MPEG1;
                    break;
                default:
                    throw new ArgumentOutOfRangeException("format", "Unsupported sample rate");
            }
            dwSampleRate = (uint)format.nSamplesPerSec;				// INPUT FREQUENCY
            switch (ReSampleRate)
            {
                case 0:  //0 means no resampling
                case 11025:
                case 12000:
                case 16000:
                case 22050:
                case 24000:
                case 32000:
                case 44100:
                case 48000:
                    break;
                default:
                    throw new ArgumentOutOfRangeException("ReSampleRate", "Unsupported resample rate");
            }
            dwReSampleRate = ReSampleRate;
            switch (format.nChannels)
            {
                case 1:
                    nMode = MpegMode.MONO;
                    break;
                case 2:
                    nMode = MpegMode.STEREO;
                    break;
                default:
                    throw new ArgumentOutOfRangeException("format", "Invalid number of channels");
            }
            switch (MpeBitRate)
            {
                case 32:
                case 40:
                case 48:
                case 56:
                case 64:
                case 80:
                case 96:
                case 112:
                case 128:
                case 160: //Allowed bit rates in MPEG1 and MPEG2
                    break;
                case 192:
                case 224:
                case 256:
                case 320: //Allowed only in MPEG1
                    if (dwMpegVersion != MPEG1)
                    {
                        throw new ArgumentOutOfRangeException("MpsBitRate", "Bit rate not compatible with input format");
                    }
                    break;
                case 8:
                case 16:
                case 24:
                case 144: //Allowed only in MPEG2
                    if (dwMpegVersion != MPEG2)
                    {
                        throw new ArgumentOutOfRangeException("MpsBitRate", "Bit rate not compatible with input format");
                    }
                    break;
                default:
                    throw new ArgumentOutOfRangeException("MpsBitRate", "Unsupported bit rate");
            }
            dwBitrate = MpeBitRate;					// MINIMUM BIT RATE
            nPreset = LAME_QUALITY_PRESET.LQP_NORMAL_QUALITY;		// QUALITY PRESET SETTING
            dwPsyModel = 0;					// USE DEFAULT PSYCHOACOUSTIC MODEL 
            dwEmphasis = 0;					// NO EMPHASIS TURNED ON
            bOriginal = 1;					// SET ORIGINAL FLAG
            bWriteVBRHeader = 0;
            bNoRes = 0;					// No Bit resorvoir
            bCopyright = 0;
            bCRC = 0;
            bEnableVBR = 0;
            bPrivate = 0;
            bStrictIso = 0;
            dwMaxBitrate = 0;
            dwVbrAbr_bps = 0;
            nQuality = 0;
            nVbrMethod = VBRMETHOD.VBR_METHOD_NONE;
            nVBRQuality = 0;
        }
    }

    [StructLayout(LayoutKind.Sequential), Serializable]
    public struct ACC
    {
        public uint dwSampleRate;
        public byte byMode;
        public ushort wBitrate;
        public byte byEncodingMethod;
    }

    [StructLayout(LayoutKind.Explicit), Serializable]
    public class Format
    {
        [FieldOffset(0)]
        public MP3 mp3;
        [FieldOffset(0)]
        public LHV1 lhv1;
        [FieldOffset(0)]
        public ACC acc;

        public Format(WaveFormat format, uint MpeBitRate, uint sampleRate)
        {
            lhv1 = new LHV1(format, MpeBitRate, sampleRate);
        }
    }

    [StructLayout(LayoutKind.Sequential), Serializable]
    public class BE_CONFIG
    {
        // encoding formats
        public const uint BE_CONFIG_MP3 = 0;
        public const uint BE_CONFIG_LAME = 256;

        public uint dwConfig;
        public Format format;

        public BE_CONFIG(WaveFormat format, uint MpeBitRate, uint sampleRate)
        {
            this.dwConfig = BE_CONFIG_LAME;
            this.format = new Format(format, MpeBitRate, sampleRate);
        }
        public BE_CONFIG(WaveFormat format)
            : this(format, 128, (uint)format.nSamplesPerSec)
        {
        }
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public class BE_VERSION
    {
        public const uint BE_MAX_HOMEPAGE = 256;
        public byte byDLLMajorVersion;
        public byte byDLLMinorVersion;
        public byte byMajorVersion;
        public byte byMinorVersion;
        // DLL Release date
        public byte byDay;
        public byte byMonth;
        public ushort wYear;
        //Homepage URL
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257/*BE_MAX_HOMEPAGE+1*/)]
        public string zHomepage;
        public byte byAlphaLevel;
        public byte byBetaLevel;
        public byte byMMXEnabled;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 125)]
        public byte[] btReserved;
        public BE_VERSION()
        {
            btReserved = new byte[125];
        }
    }

    /// <summary>
    /// Lame_enc DLL functions
    /// </summary>
    public class Lame_encDll
    {
        //Error codes
        public const uint BE_ERR_SUCCESSFUL = 0;
        public const uint BE_ERR_INVALID_FORMAT = 1;
        public const uint BE_ERR_INVALID_FORMAT_PARAMETERS = 2;
        public const uint BE_ERR_NO_MORE_HANDLES = 3;
        public const uint BE_ERR_INVALID_HANDLE = 4;

        /// <summary>
        /// This function is the first to call before starting an encoding stream.
        /// </summary>
        /// <param name="pbeConfig">Encoder settings</param>
        /// <param name="dwSamples">Receives the number of samples (not bytes, each sample is a SHORT) to send to each beEncodeChunk() on return.</param>
        /// <param name="dwBufferSize">Receives the minimun number of bytes that must have the output(result) buffer</param>
        /// <param name="phbeStream">Receives the stream handle on return</param>
        /// <returns>On success: BE_ERR_SUCCESSFUL</returns>
        [DllImport("Lame_enc.dll")]
        public static extern uint beInitStream(BE_CONFIG pbeConfig, ref uint dwSamples, ref uint dwBufferSize, ref uint phbeStream);
        /// <summary>
        /// Encodes a chunk of samples. Please note that if you have set the output to 
        /// generate mono MP3 files you must feed beEncodeChunk() with mono samples
        /// </summary>
        /// <param name="hbeStream">Handle of the stream.</param>
        /// <param name="nSamples">Number of samples to be encoded for this call. 
        /// This should be identical to what is returned by beInitStream(), 
        /// unless you are encoding the last chunk, which might be smaller.</param>
        /// <param name="pInSamples">Array of 16-bit signed samples to be encoded. 
        /// These should be in stereo when encoding a stereo MP3 
        /// and mono when encoding a mono MP3</param>
        /// <param name="pOutput">Buffer where to write the encoded data. 
        /// This buffer should be at least of the minimum size returned by beInitStream().</param>
        /// <param name="pdwOutput">Returns the number of bytes of encoded data written. 
        /// The amount of data written might vary from chunk to chunk</param>
        /// <returns>On success: BE_ERR_SUCCESSFUL</returns>
        [DllImport("Lame_enc.dll")]
        public static extern uint beEncodeChunk(uint hbeStream, uint nSamples, short[] pInSamples, [In, Out] byte[] pOutput, ref uint pdwOutput);
        /// <summary>
        /// Encodes a chunk of samples. Please note that if you have set the output to 
        /// generate mono MP3 files you must feed beEncodeChunk() with mono samples
        /// </summary>
        /// <param name="hbeStream">Handle of the stream.</param>
        /// <param name="nSamples">Number of samples to be encoded for this call. 
        /// This should be identical to what is returned by beInitStream(), 
        /// unless you are encoding the last chunk, which might be smaller.</param>
        /// <param name="pSamples">Pointer at the 16-bit signed samples to be encoded. 
        /// InPtr is used to pass any type of array without need of make memory copy, 
        /// then gaining in performance. Note that nSamples is not the number of bytes,
        /// but samples (is sample is a SHORT)</param>
        /// <param name="pOutput">Buffer where to write the encoded data. 
        /// This buffer should be at least of the minimum size returned by beInitStream().</param>
        /// <param name="pdwOutput">Returns the number of bytes of encoded data written. 
        /// The amount of data written might vary from chunk to chunk</param>
        /// <returns>On success: BE_ERR_SUCCESSFUL</returns>
        [DllImport("Lame_enc.dll")]
        protected static extern uint beEncodeChunk(uint hbeStream, uint nSamples, IntPtr pSamples, [In, Out] byte[] pOutput, ref uint pdwOutput);
        /// <summary>
        /// Encodes a chunk of samples. Samples are contained in a byte array
        /// </summary>
        /// <param name="hbeStream">Handle of the stream.</param>
        /// <param name="buffer">Bytes to encode</param>
        /// <param name="index">Position of the first byte to encode</param>
        /// <param name="nBytes">Number of bytes to encode (not samples, samples are two byte lenght)</param>
        /// <param name="pOutput">Buffer where to write the encoded data.
        /// This buffer should be at least of the minimum size returned by beInitStream().</param>
        /// <param name="pdwOutput">Returns the number of bytes of encoded data written. 
        /// The amount of data written might vary from chunk to chunk</param>
        /// <returns>On success: BE_ERR_SUCCESSFUL</returns>
        public static uint EncodeChunk(uint hbeStream, byte[] buffer, int index, uint nBytes, byte[] pOutput, ref uint pdwOutput)
        {
            uint res;
            GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
            try
            {
                IntPtr ptr = (IntPtr)(handle.AddrOfPinnedObject().ToInt32() + index);
                res = beEncodeChunk(hbeStream, nBytes / 2/*Samples*/, ptr, pOutput, ref pdwOutput);
            }
            finally
            {
                handle.Free();
            }
            return res;
        }
        /// <summary>
        /// Encodes a chunk of samples. Samples are contained in a byte array
        /// </summary>
        /// <param name="hbeStream">Handle of the stream.</param>
        /// <param name="buffer">Bytes to encode</param>
        /// <param name="pOutput">Buffer where to write the encoded data.
        /// This buffer should be at least of the minimum size returned by beInitStream().</param>
        /// <param name="pdwOutput">Returns the number of bytes of encoded data written. 
        /// The amount of data written might vary from chunk to chunk</param>
        /// <returns>On success: BE_ERR_SUCCESSFUL</returns>
        public static uint EncodeChunk(uint hbeStream, byte[] buffer, byte[] pOutput, ref uint pdwOutput)
        {
            return EncodeChunk(hbeStream, buffer, 0, (uint)buffer.Length, pOutput, ref pdwOutput);
        }
        /// <summary>
        /// This function should be called after encoding the last chunk in order to flush 
        /// the encoder. It writes any encoded data that still might be left inside the 
        /// encoder to the output buffer. This function should NOT be called unless 
        /// you have encoded all of the chunks in your stream.
        /// </summary>
        /// <param name="hbeStream">Handle of the stream.</param>
        /// <param name="pOutput">Where to write the encoded data. This buffer should be 
        /// at least of the minimum size returned by beInitStream().</param>
        /// <param name="pdwOutput">Returns number of bytes of encoded data written.</param>
        /// <returns>On success: BE_ERR_SUCCESSFUL</returns>
        [DllImport("Lame_enc.dll")]
        public static extern uint beDeinitStream(uint hbeStream, [In, Out] byte[] pOutput, ref uint pdwOutput);
        /// <summary>
        /// Last function to be called when finished encoding a stream. 
        /// Should unlike beDeinitStream() also be called if the encoding is canceled.
        /// </summary>
        /// <param name="hbeStream">Handle of the stream.</param>
        /// <returns>On success: BE_ERR_SUCCESSFUL</returns>
        [DllImport("Lame_enc.dll")]
        public static extern uint beCloseStream(uint hbeStream);
        /// <summary>
        /// Returns information like version numbers (both of the DLL and encoding engine), 
        /// release date and URL for lame_enc's homepage. 
        /// All this information should be made available to the user of your product 
        /// through a dialog box or something similar.
        /// </summary>
        /// <param name="pbeVersion"Where version number, release date and URL for homepage 
        /// is returned.</param>
        [DllImport("Lame_enc.dll")]
        public static extern void beVersion([Out] BE_VERSION pbeVersion);
        [DllImport("Lame_enc.dll", CharSet = CharSet.Ansi)]
        public static extern void beWriteVBRHeader(string pszMP3FileName);
        [DllImport("Lame_enc.dll")]
        public static extern uint beEncodeChunkFloatS16NI(uint hbeStream, uint nSamples, [In]float[] buffer_l, [In]float[] buffer_r, [In, Out]byte[] pOutput, ref uint pdwOutput);
        [DllImport("Lame_enc.dll")]
        public static extern uint beFlushNoGap(uint hbeStream, [In, Out]byte[] pOutput, ref uint pdwOutput);
        [DllImport("Lame_enc.dll", CharSet = CharSet.Ansi)]
        public static extern uint beWriteInfoTag(uint hbeStream, string lpszFileName);
    }
}
namespace SoundServant.WaveLib
{
    public enum WaveFormats
    {
        Pcm = 1,
        Float = 3
    }

    [StructLayout(LayoutKind.Sequential)]
    public class WaveFormat
    {
        public short wFormatTag;
        public short nChannels;
        public int nSamplesPerSec;
        public int nAvgBytesPerSec;
        public short nBlockAlign;
        public short wBitsPerSample;
        public short cbSize;

        public WaveFormat(int rate, int bits, short channels)
        {
            wFormatTag = (short)WaveFormats.Pcm;
            nChannels = (short)channels;
            nSamplesPerSec = rate;
            wBitsPerSample = (short)bits;
            cbSize = 0;

            nBlockAlign = (short)(channels * (bits / 8));
            nAvgBytesPerSec = nSamplesPerSec * nBlockAlign;
        }
    }

    internal class WaveNative
    {
        // consts
        public const int MMSYSERR_NOERROR = 0; // no error

        public const int MM_WOM_OPEN = 0x3BB;
        public const int MM_WOM_CLOSE = 0x3BC;
        public const int MM_WOM_DONE = 0x3BD;

        public const int CALLBACK_FUNCTION = 0x00030000;    // dwCallback is a FARPROC 

        public const int TIME_MS = 0x0001;  // time in milliseconds 
        public const int TIME_SAMPLES = 0x0002;  // number of wave samples 
        public const int TIME_BYTES = 0x0004;  // current byte offset 

        // callbacks
        public delegate void WaveDelegate(IntPtr hdrvr, int uMsg, int dwUser, ref WaveHdr wavhdr, int dwParam2);

        // structs 

        [StructLayout(LayoutKind.Sequential)]
        public struct WaveHdr
        {
            public IntPtr lpData; // pointer to locked data buffer
            public int dwBufferLength; // length of data buffer
            public int dwBytesRecorded; // used for input only
            public IntPtr dwUser; // for client's use
            public int dwFlags; // assorted flags (see defines)
            public int dwLoops; // loop control counter
            public IntPtr lpNext; // PWaveHdr, reserved for driver
            public int reserved; // reserved for driver
        }

        private const string mmdll = "winmm.dll";

        // native calls
        [DllImport(mmdll)]
        public static extern int waveOutGetNumDevs();
        [DllImport(mmdll)]
        public static extern int waveOutPrepareHeader(IntPtr hWaveOut, ref WaveHdr lpWaveOutHdr, int uSize);
        [DllImport(mmdll)]
        public static extern int waveOutUnprepareHeader(IntPtr hWaveOut, ref WaveHdr lpWaveOutHdr, int uSize);
        [DllImport(mmdll)]
        public static extern int waveOutWrite(IntPtr hWaveOut, ref WaveHdr lpWaveOutHdr, int uSize);
        [DllImport(mmdll)]
        public static extern int waveOutOpen(out IntPtr hWaveOut, int uDeviceID, WaveFormat lpFormat, WaveDelegate dwCallback, int dwInstance, int dwFlags);
        [DllImport(mmdll)]
        public static extern int waveOutReset(IntPtr hWaveOut);
        [DllImport(mmdll)]
        public static extern int waveOutClose(IntPtr hWaveOut);
        [DllImport(mmdll)]
        public static extern int waveOutPause(IntPtr hWaveOut);
        [DllImport(mmdll)]
        public static extern int waveOutRestart(IntPtr hWaveOut);
        [DllImport(mmdll)]
        public static extern int waveOutGetPosition(IntPtr hWaveOut, out int lpInfo, int uSize);
        [DllImport(mmdll)]
        public static extern int waveOutSetVolume(IntPtr hWaveOut, int dwVolume);
        [DllImport(mmdll)]
        public static extern int waveOutGetVolume(IntPtr hWaveOut, out int dwVolume);
    }
}
namespace SoundServant
{
    /// <summary>
    /// Config information for MP3 writer
    /// </summary>
    [Serializable]
    public class Mp3WriterConfig : AudioWriterConfig
    {
        private Lame.BE_CONFIG m_BeConfig;

        protected Mp3WriterConfig(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
            m_BeConfig = (Lame.BE_CONFIG)info.GetValue("BE_CONFIG", typeof(Lame.BE_CONFIG));
        }

        public Mp3WriterConfig(WaveFormat InFormat, Lame.BE_CONFIG beconfig)
            : base(InFormat)
        {
            m_BeConfig = beconfig;
        }

        public Mp3WriterConfig(WaveFormat InFormat)
            : this(InFormat, new Lame.BE_CONFIG(InFormat))
        {
        }

        public Mp3WriterConfig()
            : this(new WaveLib.WaveFormat(44100, 16, 2))
        {
        }

        public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
        {
            base.GetObjectData(info, context);
            info.AddValue("BE_CONFIG", m_BeConfig, m_BeConfig.GetType());
        }

        public Lame.BE_CONFIG Mp3Config
        {
            get
            {
                return m_BeConfig;
            }
            set
            {
                m_BeConfig = value;
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    [Serializable]
    public class AudioWriterConfig : ISerializable
    {
        protected WaveFormat m_Format;

        /// <summary>
        /// A constructor with this signature must be implemented by descendants. 
        /// <see cref="System.Runtime.Serialization.ISerializable"/> for more information
        /// </summary>
        /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/> where is the serialized data.</param>
        /// <param name="context">The source (see <see cref="System.Runtime.Serialization.StreamingContext"/>) for this serialization.</param>
        protected AudioWriterConfig(SerializationInfo info, StreamingContext context)
        {
            int rate = info.GetInt32("Format.Rate");
            int bits = info.GetInt32("Format.Bits");
            int channels = info.GetInt32("Format.Channels");
            m_Format = new WaveFormat(rate, bits, (short)channels);
        }

        public AudioWriterConfig(WaveFormat f)
        {
            m_Format = new WaveFormat(f.nSamplesPerSec, f.wBitsPerSample, f.nChannels);
        }

        public AudioWriterConfig()
            : this(new WaveFormat(44100, 16, 2))
        {
        }

        public WaveFormat Format
        {
            get
            {
                return m_Format;
            }
            set
            {
                m_Format = value;
            }
        }

        #region ISerializable Members

        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Format.Rate", m_Format.nSamplesPerSec);
            info.AddValue("Format.Bits", m_Format.wBitsPerSample);
            info.AddValue("Format.Channels", m_Format.nChannels);
        }

        #endregion
    }
}

